﻿// Copyright (c) 2012 Mokah Nnamezie & Andrew Cooper
// Neo4Net graph database - http://neo4net.codeplex.com
//
// This file is part of Neo4Net - a .Net port of the Neo4j graph database
//
// Neo4Net is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

namespace Neo4Net.Kernel.Impl.Core
{
    using Neo4Net.GraphDb;
    using Neo4Net.GraphDb.Factory;
    using Neo4Net.Kernel.Configuration;
    using Neo4Net.Kernel.Impl.Transaction;
    using Neo4Net.Kernel.Impl.Transaction.XaFramework;
    using Neo4Net.Tooling;
    using NUnit.Framework;
    using System;
    using System.Collections.Generic;
    using System.Linq;

    [TestFixture]
    public class TestNeo4Net : AbstractNeo4NetTestCase
    {
        [Test]
        public void TestReferenceNode()
        {
            // fix this test when we can set reference node again
            INode oldReferenceNode = null;
            try
            {
                // get old reference node if one is set
                oldReferenceNode = GraphDb.ReferenceNode;
            }
            catch (Exception e)
            {
                // ok no one set, oldReferenceNode is null then
            }
            INode newReferenceNode = GraphDb.CreateNode();
            NodeManager.SetReferenceNodeId(newReferenceNode.Id);
            Assert.That(GraphDb.ReferenceNode, Is.EqualTo(newReferenceNode));
            newReferenceNode.Delete();
            if (oldReferenceNode != null)
            {
                NodeManager.SetReferenceNodeId(oldReferenceNode.Id);
                Assert.That(GraphDb.ReferenceNode, Is.EqualTo(oldReferenceNode));
            }
        }

        [Test]
        public void TestBasicNodeRelationships()
        {
            INode firstNode = null;
            INode secondNode = null;
            IRelationship rel = null;
            // Create nodes and a relationship between them
            firstNode = GraphDb.CreateNode();
            Assert.That(firstNode, Is.Not.Null, "Failure creating first node");
            secondNode = GraphDb.CreateNode();
            Assert.That(secondNode, Is.Not.Null, "Failure creating second node");
            rel = firstNode.CreateRelationshipTo(secondNode, MyRelTypes.Test);
            Assert.That(rel, Is.Not.Null, "Relationship is null");
            IRelationshipType relType = rel.Type;
            Assert.That(relType, Is.Not.Null, "Relationship's type is is null");

            // Verify that the node reports that it has a relationship of
            // the type we created above
            Assert.That(firstNode.GetRelationships(relType).GetEnumerator().MoveNext(), Is.True);
            Assert.That(secondNode.GetRelationships(relType).GetEnumerator().MoveNext(), Is.True);

            IEnumerable<IRelationship> allRels = null;

            // Verify that both nodes return the relationship we created above
            allRels = firstNode.GetRelationships();
            CollectionAssert.Contains(allRels, rel);
            allRels = firstNode.GetRelationships(relType);
            CollectionAssert.Contains(allRels, rel);

            allRels = secondNode.GetRelationships();
            CollectionAssert.Contains(allRels, rel);
            allRels = secondNode.GetRelationships(relType);
            CollectionAssert.Contains(allRels, rel);

            // Verify that the relationship reports that it is associated with
            // firstNode and secondNode
            INode[] relNodes = rel.GetNodes();
            Assert.That(relNodes.Length, Is.EqualTo(2), "A relationship should always be connected to exactly two nodes");
            CollectionAssert.Contains(relNodes, firstNode, "Relationship says that it isn't connected to firstNode");
            CollectionAssert.Contains(relNodes, secondNode, "Relationship says that it isn't connected to secondNode");
            Assert.That(rel.GetOtherNode(firstNode), Is.EqualTo(secondNode), "The other node should be secondNode but it isn't");
            Assert.That(rel.GetOtherNode(secondNode), Is.EqualTo(firstNode), "The other node should be firstNode but it isn't");
            rel.Delete();
            secondNode.Delete();
            firstNode.Delete();
        }

        //    private static enum RelTypes implements IRelationship Type
        //    {
        //        ONE_MORE_RELATIONSHIP;
        //    }

        // TODO: fix this testcase
        [Test]
        public void TestIdUsageInfo()
        {
            NodeManager nm = EmbeddedGraphDb.NodeManager;
            long nodeCount = nm.GetNumberOfIdsInUse(typeof(INode));
            long relCount = nm.GetNumberOfIdsInUse(typeof(IRelationship));
            if (nodeCount > nm.GetHighestPossibleIdInUse(typeof(INode)))
            {
                // Assert.Fail( "Node count greater than highest id " + nodeCount );
            }
            if (relCount > nm.GetHighestPossibleIdInUse(typeof(IRelationship)))
            {
                // Assert.Fail( "Rel count greater than highest id " + relCount );
            }
            // Assert.That( nodeCount <= nm.GetHighestPossibleIdInUse( typeof(INode) ) , Is.True);
            // Assert.That( relCount <= nm.GetHighestPossibleIdInUse( typeof(IRelationship)), Is.True );
            INode n1 = nm.CreateNode();
            INode n2 = nm.CreateNode();
            IRelationship r1 = n1.CreateRelationshipTo(n2, MyRelTypes.Test);
            // Assert.That(nm.GetNumberOfIdsInUse( typeof(INode) ), Is.EqualTo(nodeCount + 2) );
            // Assert.That(nm.GetNumberOfIdsInUse(typeof(IRelationship) ), Is.EqualTo(relCount + 1) );
            r1.Delete();
            n1.Delete();
            n2.Delete();
            // must commit for ids to be reused
            Transaction.Success();
            Transaction.Finish();
            // Assert.That( nodeCount, nm.GetNumberOfIdsInUse( typeof(INode) ), Is.EqualTo(nodeCount) );
            // Assert.That( relCount, nm.GetNumberOfIdsInUse( typeof(IRelationship) ), Is.EqualTo(relCount) );
            Transaction = GraphDb.BeginTx();
        }

        [Test]
        public void TestRandomPropertyName()
        {
            INode node1 = GraphDb.CreateNode();
            String key = "random_" + new Random().Next();
            node1.SetProperty(key, "value");
            Assert.That(node1.GetProperty(key), Is.EqualTo("value"));
            node1.Delete();
        }

        [Test]
        public void TestNodeChangePropertyArray()
        {
            ITransaction tx = Transaction;
            tx.Finish();
            tx = GraphDb.BeginTx();
            INode node;
            try
            {
                node = GraphDb.CreateNode();
                tx.Success();
            }
            finally
            {
                tx.Finish();
            }
            tx = GraphDb.BeginTx();
            try
            {
                node.SetProperty("test", new String[] { "value1" });
                tx.Success();
            }
            finally
            {
                tx.Finish();
            }
            tx = GraphDb.BeginTx();
            try
            {
                node.SetProperty("test", new String[] { "value1", "value2" });
                // no success, we wanna test rollback on this operation
            }
            finally
            {
                tx.Finish();
            }
            tx = GraphDb.BeginTx();
            try
            {
                String[] value = (String[])node.GetProperty("test");
                Assert.That(value.Length, Is.EqualTo(1));
                Assert.That(value[0], Is.EqualTo("value1"));
                tx.Success();
            }
            finally
            {
                tx.Finish();
            }
            Transaction = GraphDb.BeginTx();
        }

        [Test]
        public void TestMultipleNeos()
        {
            string storePath = GetStorePath("test-neo2");
            DeleteFileOrDirectory(storePath);
            IGraphDatabaseService graphDb2 = new GraphDatabaseFactory().NewEmbeddedDatabase(storePath);
            ITransaction tx2 = graphDb2.BeginTx();
            GraphDb.CreateNode();
            graphDb2.CreateNode();
            tx2.Success();
            tx2.Finish();
            graphDb2.Shutdown();
        }

        [Test]
        public void TestGetAllNodes()
        {
            long highId = NodeManager.GetHighestPossibleIdInUse(typeof(INode));
            if (highId >= 0 && highId < 10000)
            {
                int count = GlobalGraphOperations.At(GraphDb).GetAllNodes().Count();
                bool found = false;
                INode newNode = GraphDb.CreateNode();
                NewTransaction();
                int oldCount = count;
                count = 0;
                foreach (INode node in GlobalGraphOperations.At(GraphDb).GetAllNodes())
                {
                    count++;
                    if (node.Equals(newNode))
                    {
                        found = true;
                    }
                }
                Assert.That(found, Is.True);
                Assert.That(oldCount + 1, Is.EqualTo(count));

                // Tests a bug in the "all nodes" iterator
                IEnumerator<INode> allNodesIterator = GlobalGraphOperations.At(GraphDb).GetAllNodes().GetEnumerator();
                Assert.That(allNodesIterator.MoveNext(), Is.True);

                newNode.Delete();
                NewTransaction();
                found = false;
                count = 0;
                foreach (INode node in GlobalGraphOperations.At(GraphDb).GetAllNodes())
                {
                    count++;
                    if (node.Equals(newNode))
                    {
                        found = true;
                    }
                }
                Assert.That(found, Is.False);
                Assert.That(oldCount, Is.EqualTo(count));
            }
            // else we skip test, takes too long
        }

        [Test]
        public void TestMultipleShutdown()
        {
            GraphDb.Shutdown();
            GraphDb.Shutdown();
        }

        [Test]
        public void TestKeepLogsConfig()
        {
            IDictionary<string, string> config = new Dictionary<string, string>();
            config.Add(GraphDatabaseSettings.KeepLogicalLogs.Name, Config.DefaultDataSourceName);
            String storeDir = "target/configdb";
            DeleteFileOrDirectory(storeDir);
            GraphDatabaseFactory graphDatabaseFactory = new GraphDatabaseFactory();
            IGraphDatabaseAPI db = (IGraphDatabaseAPI)graphDatabaseFactory.NewEmbeddedDatabaseBuilder(storeDir).SetConfig(config).NewGraphDatabase();
            XaDataSourceManager xaDsMgr = db.XaDataSourceManager;
            XaDataSource xaDs = xaDsMgr.GetNeoStoreDataSource();
            Assert.That(xaDs.IsLogicalLogKept(), Is.True);
            db.Shutdown();

            config.Remove(GraphDatabaseSettings.KeepLogicalLogs.Name);
            db = (IGraphDatabaseAPI)graphDatabaseFactory.NewEmbeddedDatabaseBuilder(storeDir).SetConfig(config).NewGraphDatabase();
            xaDsMgr = db.XaDataSourceManager;
            xaDs = xaDsMgr.GetNeoStoreDataSource();
            // Here we rely on the default value being set to true due to the existence
            // of previous log files.
            Assert.That(xaDs.IsLogicalLogKept(), Is.True);
            db.Shutdown();

            config.Add(GraphDatabaseSettings.KeepLogicalLogs.Name, GraphDatabaseSetting.False);
            db = (IGraphDatabaseAPI)graphDatabaseFactory.NewEmbeddedDatabaseBuilder(storeDir).SetConfig(config).NewGraphDatabase();
            xaDsMgr = db.XaDataSourceManager;
            xaDs = xaDsMgr.GetNeoStoreDataSource();
            // Here we explicitly turn off the keeping of logical logs so it should be
            // false even if there are previous existing log files.
            Assert.That(xaDs.IsLogicalLogKept(), Is.False);
            db.Shutdown();

            config.Add(GraphDatabaseSettings.KeepLogicalLogs.Name, Config.DefaultDataSourceName + "=false");
            db = (IGraphDatabaseAPI)graphDatabaseFactory.NewEmbeddedDatabaseBuilder(storeDir).SetConfig(config).NewGraphDatabase();
            xaDsMgr = db.XaDataSourceManager;
            xaDs = xaDsMgr.GetNeoStoreDataSource();
            // Here we explicitly turn off the keeping of logical logs so it should be
            // false even if there are previous existing log files.
            Assert.That(xaDs.IsLogicalLogKept(), Is.False);
            db.Shutdown();

            config.Add(GraphDatabaseSettings.KeepLogicalLogs.Name, Config.DefaultDataSourceName + "=true");
            db = (IGraphDatabaseAPI)graphDatabaseFactory.NewEmbeddedDatabaseBuilder(storeDir).SetConfig(config).NewGraphDatabase();
            xaDsMgr = db.XaDataSourceManager;
            xaDs = xaDsMgr.GetNeoStoreDataSource();
            Assert.That(xaDs.IsLogicalLogKept(), Is.True);
            db.Shutdown();

            config.Add(GraphDatabaseSettings.KeepLogicalLogs.Name, "true");
            db = (IGraphDatabaseAPI)graphDatabaseFactory.NewEmbeddedDatabaseBuilder(storeDir).SetConfig(config).NewGraphDatabase();
            xaDsMgr = db.XaDataSourceManager;
            xaDs = xaDsMgr.GetNeoStoreDataSource();
            Assert.That(xaDs.IsLogicalLogKept(), Is.True);
            db.Shutdown();
        }
    }
}
